package edu.moravian.math;

import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
import org.junit.Test;

/**
 *
 * @author Elliot
 */
public class Point2DTest 
{
    private static final Point2D zeroPoint = new Point2D();
    private static final Point2D quadrant1Point = new Point2D(2.0, 2.0);
    private static final Point2D quadrant2Point = new Point2D(-2.0, 2.0);
    private static final Point2D quadrant3Point = new Point2D(-2.0, -2.0);
    private static final Point2D quadrant4Point= new Point2D(2.0, -2.0);
    
    private static final Vector2D quadrant1Vector = new Vector2D(2.0, 2.0);
    private static final Vector2D quadrant2Vector = new Vector2D(-2.0, 2.0);
    private static final Vector2D quadrant3Vector = new Vector2D(-2.0, -2.0);
    private static final Vector2D quadrant4Vector = new Vector2D(2.0, -2.0);
    
    private static final double DOUBLE_TOL = Point2D.TOL;
    private static final double ZERO_TOL = 0.0;
    
    
    @Test
    public void testSimplePoint()
    {
        //test the constructors and getters 
        Point2D p = new Point2D();
        assertEquals(0.0, p.getX(), ZERO_TOL);
        assertEquals(0.0, p.getY(), ZERO_TOL);
        
        Point2D p2 = new Point2D(1.0, 2.0);
        assertEquals(1.0, p2.getX(), DOUBLE_TOL);
        assertEquals(2.0, p2.getY(), DOUBLE_TOL);
        
        Point2D p3 = new Point2D(p2);
        assertEquals(1.0, p3.getX(), DOUBLE_TOL);
        assertEquals(2.0, p3.getY(), DOUBLE_TOL);
        
        Point2D p4 = new Point2D(0.0, 0.0);
        assertEquals(0.0, p4.getX(), DOUBLE_TOL);
        assertEquals(0.0, p4.getY(), DOUBLE_TOL);
    }
    
    
    
    
    @Test
    public void testSimplePointMutators()
    {
        //test a simple set for x and make sure y isn't changed
        Point2D p = new Point2D();
        assertEquals(0.0, p.getX(), ZERO_TOL);
        assertEquals(0.0, p.getY(), ZERO_TOL);
        
        p.setX(2.5);
        assertEquals(2.5, p.getX(), ZERO_TOL);
        assertEquals(0.0, p.getY(), ZERO_TOL);
        
        //test a simple set for y and make sure x isn't changed 
        p.setY(5.2);
        assertEquals(2.5, p.getX(), ZERO_TOL);
        assertEquals(5.2, p.getY(), ZERO_TOL);
        
        //test multiple sets for y
        p.setY(4.0);
        assertEquals(2.5, p.getX(), ZERO_TOL);
        assertEquals(4.0, p.getY(), ZERO_TOL);
        
        //ensure that setting a whole point works
        Point2D p2 = new Point2D(-4.2, -2.4);
        assertEquals(-4.2, p2.getX(), ZERO_TOL);
        assertEquals(-2.4, p2.getY(), ZERO_TOL);
        
        p.set(p2);
        assertEquals(-4.2, p.getX(), ZERO_TOL);
        assertEquals(-2.4, p.getY(), ZERO_TOL);
        assertEquals(-4.2, p2.getX(), ZERO_TOL);
        assertEquals(-2.4, p2.getY(), ZERO_TOL);
        
        //ensure that changing one object does not 
        //modify one that was created from it 
        p2.setX(12.01);
        assertEquals(-4.2, p.getX(), ZERO_TOL);
        assertEquals(-2.4, p.getY(), ZERO_TOL);
        assertEquals(12.01, p2.getX(), ZERO_TOL);
        assertEquals(-2.4, p2.getY(), ZERO_TOL);
        
    }
    
    
    
  
    
    @Test
    public void testMinus()
    {
        //check that the vector between a point and itself is zero
        Vector2D oneMinusOne = quadrant1Point.minus(quadrant1Point);
        assertEquals(2.0, quadrant1Point.getX(), DOUBLE_TOL);
        assertEquals(2.0, quadrant1Point.getY(), DOUBLE_TOL);
        assertEquals(0.0, oneMinusOne.getX(), DOUBLE_TOL);
        assertEquals(0.0, oneMinusOne.getY(), DOUBLE_TOL);
        assertTrue(oneMinusOne.equals(Vector2D.zero));
    
        //check the vector between two points and the opposite order
        Vector2D oneMinusZero = quadrant1Point.minus(zeroPoint);
        assertEquals(2.0, quadrant1Point.getX(), DOUBLE_TOL);
        assertEquals(2.0, quadrant1Point.getY(), DOUBLE_TOL);
        assertEquals(2.0, oneMinusZero.getX(), DOUBLE_TOL);
        assertEquals(2.0, oneMinusZero.getY(), DOUBLE_TOL);
    
        Vector2D ZeroMinusOne = zeroPoint.minus(quadrant1Point);
        assertEquals(-2.0, ZeroMinusOne.getX(), DOUBLE_TOL);
        assertEquals(-2.0, ZeroMinusOne.getY(), DOUBLE_TOL);
        
        
        //test a few vectors acros quadrants
        Vector2D oneMinusTwo = quadrant1Point.minus(quadrant2Point);
        assertEquals(4.0, oneMinusTwo.getX(), DOUBLE_TOL);
        assertEquals(0.0, oneMinusTwo.getY(), DOUBLE_TOL);
    
        Vector2D twoMinusOne = quadrant2Point.minus(quadrant1Point);
        assertEquals(-4.0, twoMinusOne.getX(), DOUBLE_TOL);
        assertEquals(0.0, twoMinusOne.getY(), DOUBLE_TOL);
        
        
        Vector2D oneMinusThree = quadrant1Point.minus(quadrant3Point);
        assertEquals(4.0, oneMinusThree.getX(), DOUBLE_TOL);
        assertEquals(4.0, oneMinusThree.getY(), DOUBLE_TOL);
        
        
        Vector2D threeMinusOne = quadrant3Point.minus(quadrant1Point);
        assertEquals(-4.0, threeMinusOne.getX(), DOUBLE_TOL);
        assertEquals(-4.0, threeMinusOne.getY(), DOUBLE_TOL);
        
    }
    
    @Test
    public void testPlusEquals()
    {
        Point2D p = new Point2D(quadrant2Point);
        
        p.plusEquals(quadrant1Vector);
        assertEquals(0.0, p.getX(), DOUBLE_TOL);
        assertEquals(4.0, p.getY(), DOUBLE_TOL);
        
        p.plusEquals(quadrant2Vector);
        assertEquals(-2.0, p.getX(), DOUBLE_TOL);
        assertEquals(6.0, p.getY(), DOUBLE_TOL);
        
        p.plusEquals(quadrant3Vector);
        assertEquals(-4.0, p.getX(), DOUBLE_TOL);
        assertEquals(4.0, p.getY(), DOUBLE_TOL);
        
        p.plusEquals(quadrant4Vector);
        assertEquals(-2.0, p.getX(), DOUBLE_TOL);
        assertEquals(2.0, p.getY(), DOUBLE_TOL);
    }
    
    
    
    @Test 
    public void testPlus()
    {
        Point2D p = new Point2D(quadrant2Point);
        
        Point2D pPlus1 = p.plus(quadrant1Vector);
        assertTrue(p.equals(quadrant2Point));
        assertEquals(0.0, pPlus1.getX(), DOUBLE_TOL);
        assertEquals(4.0, pPlus1.getY(), DOUBLE_TOL);
        
        Point2D pPlus2 = p.plus(quadrant2Vector);
        assertTrue(p.equals(quadrant2Point));
        assertEquals(-4.0, pPlus2.getX(), DOUBLE_TOL);
        assertEquals(4.0, pPlus2.getY(), DOUBLE_TOL);
        
        Point2D pPlus3 = p.plus(quadrant3Vector);
        assertTrue(p.equals(quadrant2Point));
        assertEquals(-4.0, pPlus3.getX(), DOUBLE_TOL);
        assertEquals(0.0, pPlus3.getY(), DOUBLE_TOL);
        
        Point2D pPlus4 = p.plus(quadrant4Vector);
        assertTrue(p.equals(quadrant2Point));
        assertEquals(0.0, pPlus4.getX(), DOUBLE_TOL);
        assertEquals( 0.0, pPlus4.getY(), DOUBLE_TOL);
    }

    @Test
    public void testScalePlusEquals()
    {
        double scaler = 1.5;

        //for vectors going in all directions make sure that scaleplus does
        //not change the original point and the new point has the correct values
        Point2D p = new Point2D(quadrant1Point);

        p.scalePlusEquals(scaler, quadrant1Vector);
        assertEquals(5.0, p.getX(), DOUBLE_TOL);
        assertEquals(5.0, p.getY(), DOUBLE_TOL);

        p.scalePlusEquals(scaler, quadrant2Vector);
        assertEquals(2.0, p.getX(), DOUBLE_TOL);
        assertEquals(8.0, p.getY(), DOUBLE_TOL);

        p.scalePlusEquals(scaler*4.0, quadrant3Vector);
        assertEquals(-10.0, p.getX(), DOUBLE_TOL);
        assertEquals( -4.0, p.getY(),DOUBLE_TOL);

        p.scalePlusEquals(scaler*4.0, quadrant4Vector);
        assertEquals(2.0, p.getX(), DOUBLE_TOL);
        assertEquals(-16.0, p.getY(), DOUBLE_TOL);
    }
    
    
    @Test
    public void testScalePlus()
    {
        double scaler = 1.5;

        //for vectors going in all directions make sure that scaleplus does
        //not change the original point and the new point has the correct values
        Point2D p = new Point2D(quadrant1Point);

        Point2D pScale = p.scalePlus(scaler, quadrant1Vector);
        assertTrue(p.equals(quadrant1Point));
        assertEquals(5.0, pScale.getX(), DOUBLE_TOL);
        assertEquals(5.0, pScale.getY(), DOUBLE_TOL);

        Point2D pScale2 = p.scalePlus(scaler, quadrant2Vector);
        assertTrue(p.equals(quadrant1Point));
        assertEquals(-1.0,pScale2.getX(),  DOUBLE_TOL);
        assertEquals(5.0, pScale2.getY(), DOUBLE_TOL);

        Point2D pScale3 = p.scalePlus(scaler, quadrant3Vector);
        assertTrue(p.equals(quadrant1Point));
        assertEquals( -1.0, pScale3.getX(),DOUBLE_TOL);
        assertEquals(-1.0, pScale3.getY(), DOUBLE_TOL);

        Point2D pScale4 = p.scalePlus(scaler, quadrant4Vector);
        assertTrue(p.equals(quadrant1Point));
        assertEquals(5.0, pScale4.getX(), DOUBLE_TOL);
        assertEquals(-1.0, pScale4.getY(), DOUBLE_TOL);
       
    }
            
    
    
    @Test
    public void testInWorldModEquals()
    {
        double xMax = 800.0;
        double yMax = 600.0;
        
        //do not modify points that are in world
        Point2D p = new Point2D(100.0, 200.0);
        assertEquals(100.0, p.getX(), DOUBLE_TOL);
        assertEquals(200.0, p.getY(), DOUBLE_TOL);
        
        p.modEquals(xMax, yMax);
        assertEquals(100.0, p.getX(), DOUBLE_TOL);
        assertEquals(200.0, p.getY(), DOUBLE_TOL);
        
        //do not modify top right 
        p = new Point2D(799.9, 599.9);
        assertEquals(799.9, p.getX(), DOUBLE_TOL);
        assertEquals(599.9, p.getY(), DOUBLE_TOL);
        
        p.modEquals(xMax, yMax);
        assertEquals(799.9, p.getX(), DOUBLE_TOL);
        assertEquals(599.9, p.getY(), DOUBLE_TOL);
        
        //do not modify top left
        p = new Point2D(0.0, 599.9);
        assertEquals(0.0, p.getX(), DOUBLE_TOL);
        assertEquals(599.9, p.getY(), DOUBLE_TOL);
        
        p.modEquals(xMax, yMax);
        assertEquals(0.0, p.getX(), DOUBLE_TOL);
        assertEquals(599.9, p.getY(), DOUBLE_TOL);
        
        
        
        //do not modify bottom right
        p = new Point2D(799.9, 0.0);
        assertEquals(799.9, p.getX(), DOUBLE_TOL);
        assertEquals(0.0, p.getY(), DOUBLE_TOL);
        
        p.modEquals(xMax, yMax);
        assertEquals(799.9, p.getX(), DOUBLE_TOL);
        assertEquals(0.0, p.getY(), DOUBLE_TOL);
        
        
        //do not modify bottom left
        p = new Point2D(0.0, 0.0);
        assertEquals(0.0, p.getX(), DOUBLE_TOL);
        assertEquals(0.0, p.getY(), DOUBLE_TOL);
        
        p.modEquals(xMax, yMax);
        assertEquals(0.0, p.getX(), DOUBLE_TOL);
        assertEquals(0.0, p.getY(), DOUBLE_TOL);
    }
    
    
    @Test 
    public void testOutOfWorldModEquals()
    {
        double xMax = 800.0;
        double yMax = 600.0;
        
        //modify left 
        Point2D left = new Point2D(-10.0, 300.0);
        assertEquals(-10.0, left.getX(), DOUBLE_TOL);
        assertEquals(300.0, left.getY(), DOUBLE_TOL);
        
        left.modEquals(xMax, yMax);
        assertEquals(790.0, left.getX(), DOUBLE_TOL);
        assertEquals(300.0, left.getY(), DOUBLE_TOL);
        
        //modify right 
        Point2D right = new Point2D(810.0, 300.0);
        assertEquals(810.0, right.getX(), DOUBLE_TOL);
        assertEquals(300.0, right.getY(), DOUBLE_TOL);
        
        right.modEquals(xMax, yMax);
        assertEquals(10.0, right.getX(), DOUBLE_TOL);
        assertEquals(300.0, right.getY(), DOUBLE_TOL);
        
        
        //modify top
        Point2D top = new Point2D(400.0, 610.0);
        assertEquals(400.0, top.getX(), DOUBLE_TOL);
        assertEquals(610.0, top.getY(), DOUBLE_TOL);
        
        top.modEquals(xMax, yMax);
        assertEquals(400.0, top.getX(), DOUBLE_TOL);
        assertEquals(10.0, top.getY(), DOUBLE_TOL);
        
        
        //modify bottom
        Point2D bottom = new Point2D(400.0, -10.0);
        assertEquals(400.0, bottom.getX(), DOUBLE_TOL);
        assertEquals(-10.0, bottom.getY(), DOUBLE_TOL);
        
        bottom.modEquals(xMax, yMax);
        assertEquals(400.0, bottom.getX(), DOUBLE_TOL);
        assertEquals(590.0, bottom.getY(), DOUBLE_TOL);
        
        
        
        xMax = 10;
        yMax = 5;
        
        //modify far top left
        Point2D topLeft = new Point2D(-20.0, 16.0);
        assertEquals(-20.0, topLeft.getX(), DOUBLE_TOL);
        assertEquals(16.0, topLeft.getY(), DOUBLE_TOL);
        
        topLeft.modEquals(xMax, yMax);
        assertEquals(0.0, topLeft.getX(), DOUBLE_TOL);
        assertEquals(1.0, topLeft.getY(), DOUBLE_TOL);
        
        
        //modify far top right
        Point2D topRight = new Point2D(22.0, 17.5);
        assertEquals(22.0, topRight.getX(), DOUBLE_TOL);
        assertEquals(17.5, topRight.getY(), DOUBLE_TOL);
        
        topRight.modEquals(xMax, yMax);
        assertEquals(2.0, topRight.getX(), DOUBLE_TOL);
        assertEquals(2.5, topRight.getY(), DOUBLE_TOL);
        
        
        
        //modify far bottom left
        Point2D bottomLeft = new Point2D(-17.5, -11.0);
        assertEquals(-17.5, bottomLeft.getX(), DOUBLE_TOL);
        assertEquals(-11.0, bottomLeft.getY(), DOUBLE_TOL);
        
        bottomLeft.modEquals(xMax, yMax);
        assertEquals(2.5, bottomLeft.getX(), DOUBLE_TOL);
        assertEquals(4, bottomLeft.getY(), DOUBLE_TOL);
        
        
        //modify far bottom right
        Point2D bottomRight = new Point2D(-12.4, 12.4);
        assertEquals(-12.4, bottomRight.getX(), DOUBLE_TOL);
        assertEquals(12.4, bottomRight.getY(), DOUBLE_TOL);
        
        bottomRight.modEquals(xMax, yMax);
        assertEquals(7.6, bottomRight.getX(), DOUBLE_TOL);
        assertEquals(2.4, bottomRight.getY(), DOUBLE_TOL);
    }
    
 
    
    @Test
    public void testModWithNegativeUpperBounds()
    {
        Point2D p = new Point2D();
       
        try
        {
            p.modEquals(-5, -5);
            fail("Should thrown illegal arg exception");
        }
        catch(IllegalArgumentException e)
        {
            //good
        }
        
        try
        {
            p.modEquals(-5, 5);
            fail("Should thrown illegal arg exception");
        }
        catch(IllegalArgumentException e)
        {
            //good
        }
        
        try
        {
            p.modEquals(5, -5);
            fail("Should thrown illegal arg exception");
        }
        catch(IllegalArgumentException e)
        {
            //good
        }
    }
    //we violate the transitive property of equals
    @Test
    public void testEquals()
    {
        //check that incompatible types are not equal
        assertFalse(quadrant1Point.equals(quadrant1Vector));
        //a simple false check
        assertFalse(quadrant1Point.equals(quadrant2Point));
        //check that a point is equal to itself
        assertTrue(quadrant1Point.equals(quadrant1Point));
        
        //a bunch of "zero" points
        Point2D trueZero2 = new Point2D();
        Point2D trueZero3 = new Point2D(0.0, 0.0);
        Point2D trueZero4 = new Point2D(trueZero2);
        Point2D largestZero = new Point2D(DOUBLE_TOL-getMachineEpsilon(), DOUBLE_TOL-getMachineEpsilon());
        
        List<Point2D> zeroPoints = new ArrayList<Point2D>();
        zeroPoints.add(zeroPoint);
        zeroPoints.add(trueZero2);
        zeroPoints.add(trueZero3);
        zeroPoints.add(trueZero4);
        zeroPoints.add(largestZero);
        
        //all of the zero points should be equal to each other
        for(Point2D p: zeroPoints)
        {
            for(Point2D p2: zeroPoints)
            {
               assertTrue(p.equals(p2));
            }
        }
        
        //create a smallest positive and negative non zero value based on 
        //a helper method that gets a rough estimate for the machine epsilon
        Point2D smallestNonZero = new Point2D(DOUBLE_TOL, 0.0);
        Point2D smallestNegativeNonZero = new Point2D( 0.0,-DOUBLE_TOL);
       
        //the smallest positive non zero should not be equal to any of the true zeros
        //but is equal to the largest zero, which is an interesting thing to think about
        //I'm pretty sure this breaks what is supposed to be the contrat for equals
        //if a.equals(b) and b.equals(c) then a.equals(c) should hold and that isn't 
        //neccisarily true but this is an unfortunete consequence of floating point numbers
        //although something to be aware of
        assertFalse(zeroPoint.equals(smallestNonZero));
        assertFalse(trueZero2.equals(smallestNonZero));
        assertFalse(trueZero3.equals(smallestNonZero));
        assertFalse(trueZero4.equals(smallestNonZero));
        assertTrue(largestZero.equals(smallestNonZero));
        
        assertFalse(zeroPoint.equals(smallestNegativeNonZero));
        assertFalse(trueZero2.equals(smallestNegativeNonZero));
        assertFalse(trueZero3.equals(smallestNegativeNonZero));
        assertFalse(trueZero4.equals(smallestNegativeNonZero));
        assertFalse(largestZero.equals(smallestNegativeNonZero));
        
        
    }
    
    //we violate the contract of hash code so the
    //testing is a little interesting
    
    //Objects that are equal should have the same hash value
    //... but we cant say that, all we can say is that the same 
    //object or objects that are "true" equals should have the same hash code 
    @Test
    public void testHashCode()
    {
        Point2D p1 = new Point2D();
        Point2D p2 = new Point2D(0.0, 0.0);
        Point2D p3 = new Point2D(p1);
        
        
        assertEquals(p1.hashCode(), p1.hashCode());
        assertEquals(p1.hashCode(), p2.hashCode());
        assertEquals(p1.hashCode(), p3.hashCode());
    }
    
    @Test
    public void testToString()
    {
        double x = 1.3;
        double y = 1.5;
        Point2D p = new Point2D(x, y);
        String s = "(1.3, 1.5)";
        
        assertEquals(s, p.toString());
    }
    
    @Test
    public void testRandomPoint()
    {
        double xMin = 100.0;
        double xMax = 200.0;
        double yMin = 0.0;
        double yMax = 100.0;
            
        Point2D p;
        
        for(int i=0; i<10000; i++)
        {
            p = Point2D.randomPoint(xMin, xMax, yMin, yMax);
            
            assertTrue(p.getX() < xMax && p.getX() >= xMin);
            assertTrue(p.getY() < yMax && p.getY() >= yMin);
        }
        
        p = Point2D.randomPoint(0.0, 0.0, 1.0, 1.0);
        assertEquals(p.getX(), 0.0, ZERO_TOL);
        assertEquals(p.getY(), 1.0, ZERO_TOL);
        
        p = Point2D.randomPoint(0.0, 0.0+DOUBLE_TOL, 1.0, 1.0+DOUBLE_TOL);
        assertEquals(p.getX(), 0.0, DOUBLE_TOL);
        assertEquals(p.getY(), 1.0, DOUBLE_TOL);
    }
    
    @Test 
    public void testBackwardsRandomPoint()
    {
        double xMin = 200.0;
        double xMax = 100.0;
        double yMin = 100.0;
        double yMax = 0.0;
            
        Point2D p;
        
        for(int i=0; i<10000; i++)
        {
            p = Point2D.randomPoint(xMin, xMax, yMin, yMax);
            
            assertTrue(p.getX() > xMax && p.getX() <= xMin);
            assertTrue(p.getY() > yMax && p.getY() <= yMin);
        }
    }
    
    @Test
    public void testNegativeRandomPoint()
    {
        double xMin = -200.0;
        double xMax = -100.0;
        double yMin = -100.0;
        double yMax = 0.0;
            
        Point2D p;
        
        for(int i=0; i<10000; i++)
        {
            p = Point2D.randomPoint(xMin, xMax, yMin, yMax);
            
            assertTrue(p.getX() < xMax && p.getX() >= xMin);
            assertTrue(p.getY() < yMax && p.getY() >= yMin);
        }
        
        
        
        
    }
    
    
    //This isn't really a valid test because it
    //could very well run forever with true random numbers
    //this was really just a sanity check for me
    @Test
    public void testRandomPointDistribution()
    {
        double xMin = -200.0;
        double xMax = -100.0;
        double yMin = -100.0;
        double yMax = 0.0;
            
        Point2D p;
        
        do
        {
            p = Point2D.randomPoint(xMin, xMax, yMin, yMax);
            
        }while(p.getX() > xMin+.5);
        
        do
        {
            p = Point2D.randomPoint(xMin, xMax, yMin, yMax);
        }while(p.getX() < xMax-.5);
        
        do
        {
            p = Point2D.randomPoint(xMin, xMax, yMin, yMax);
        }while(p.getY() > yMin+.5);
        
        do
        {
            p = Point2D.randomPoint(xMin, xMax, yMin, yMax);
        }while(p.getY() < yMax-.5);
    }
    
    private double getMachineEpsilon()
    {
        double machEps = 1.0;
 
        do
        {
           machEps /= 2.0;
        }
        while ((double) (1.0 + (machEps / 2.0)) != 1.0);
 
        return machEps;
    }
    
       
       
}
  




/*
 * Points will either be at 0,0 or in the 1, 2, 3,  or 4th quadrant
 * 
 * Test basic creation, getting and setting
 * 
 * Create a point from another point, change points and make sure
 * they don't change each other 
 * 
 * 
 * 
 */